﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace SortingNetworkSimulator.Model
{
    public class Network
    {
        private List<Comparator> comparators;
        private int currentMaxLayerIndex;
        
        public int InputsCount { get; private set; }

        public ReadOnlyCollection<Comparator> Comparators
        {
            get
            {
                return new ReadOnlyCollection<Comparator>(comparators);
            }
        }

        public Dictionary<int, int> LayersDepths { get; private set; }

        public Network(int inputsCount)
        {
            comparators = new List<Comparator>();
            LayersDepths = new Dictionary<int, int>();
            InputsCount = inputsCount;
            currentMaxLayerIndex = 0;
        }

        public void AddComparator(int wireOneIndex, int wireTwoIndex, int layerIndex)
        {
            if (wireOneIndex >= InputsCount || wireTwoIndex >= InputsCount)
            {
                throw new ArgumentException("One of the wires indexes is out of bound");
            }

            if (CanAddComparator(wireOneIndex, wireTwoIndex, layerIndex))
            {
                comparators.Add(new Comparator(wireOneIndex, wireTwoIndex,
                layerIndex,
                GetDrawingLayerIndexForWires(wireOneIndex, wireTwoIndex, layerIndex)));

                if (layerIndex > currentMaxLayerIndex)
                {
                    currentMaxLayerIndex = layerIndex;
                }
            }
            else
            {
                throw new InvalidOperationException("Can not add comparator");
            }
        }

        public void Process(double[] input)
        {
            if (input.Length > InputsCount)
            {
                throw new ArgumentException("Input length is too big");
            }

            foreach (Comparator comparator in comparators.OrderBy(c => c.LayerIndex))
            {
                comparator.Compare(input);
            }
        }

        public void RemoveComparator()
        {
            Comparator toRemove = comparators.Where(c => c.LayerIndex == currentMaxLayerIndex).Reverse().FirstOrDefault();

            if (toRemove != null)
            {
                comparators.Remove(toRemove);

                if (comparators.Where(c => c.LayerIndex == currentMaxLayerIndex).Count() == 0)
                {
                    currentMaxLayerIndex--;
                }
            }
            else
            {
                throw new InvalidOperationException("You trying to remove comparator that does not exist");
            }
        }

        private bool CanAddComparator(int wireOneIndex, int wireTwoIndex, int layerIndex)
        {
            if (layerIndex > currentMaxLayerIndex + 1)
            {
                return false;
            }

            IEnumerable<Comparator> comparatorsInLayer = comparators.Where(c => c.LayerIndex == layerIndex);
   
            foreach (Comparator comparator in comparatorsInLayer)
            {
                if (comparator.WireOneIndex == wireOneIndex || comparator.WireOneIndex == wireTwoIndex
                    || comparator.WireTwoIndex == wireOneIndex || comparator.WireTwoIndex == wireTwoIndex)
                {
                    return false;
                }
            }

            if (layerIndex > 0)
            {
                bool result = false;

                foreach (Comparator comparator in comparators.Where(c => c.LayerIndex == layerIndex - 1))
                {
                    if (comparator.WireOneIndex == wireOneIndex || comparator.WireOneIndex == wireTwoIndex
                    || comparator.WireTwoIndex == wireOneIndex || comparator.WireTwoIndex == wireTwoIndex)
                    {
                        result = true;
                    }
                }

                return result;
            }

            return true;
        }

        private int GetDrawingLayerIndexForWires(int wireOneIndex, int wireTwoIndex, int layerIndex)
        {
            int drawingLayerIndex = -1;
            bool isEnd = false;

            while (!isEnd)
            {
                isEnd = true;
                drawingLayerIndex++;

                IEnumerable<Comparator> comparatorsInDrawingLayer = comparators
                    .Where(c => c.DrawingLayerIndex == drawingLayerIndex
                    && c.LayerIndex == layerIndex);

                if (comparatorsInDrawingLayer.Count() == 0)
                {
                    UpdateLayerDepth(layerIndex, drawingLayerIndex);

                    return drawingLayerIndex;
                }

                IEnumerable<Comparator> comparatorsToCheck = comparatorsInDrawingLayer.Where(c => c.WireOneIndex > wireOneIndex
                    && c.WireTwoIndex < wireTwoIndex);

                int minWireOneIndex = -1, maxWireTwoIndex = -1;

                if (comparatorsToCheck.Count() > 0)
                {
                    minWireOneIndex = comparatorsToCheck.Min(c => c.WireOneIndex);
                    maxWireTwoIndex = comparatorsToCheck.Max(c => c.WireTwoIndex);
                }

                foreach (Comparator comparator in comparatorsInDrawingLayer)
                {
                    if ( (wireOneIndex >= comparator.WireOneIndex && wireOneIndex <= comparator.WireTwoIndex)
                        || (wireTwoIndex >= comparator.WireOneIndex && wireTwoIndex <= comparator.WireTwoIndex)
                        || (wireOneIndex < minWireOneIndex && wireTwoIndex > maxWireTwoIndex))
                    {
                        isEnd = false;
                    }
                }
            }

            UpdateLayerDepth(layerIndex, drawingLayerIndex);

            return drawingLayerIndex;
        }

        private bool CheckIfWiresUsed(Comparator comparator, int wireOneIndex, int wireTwoIndex)
        {
            return comparator.WireOneIndex == wireOneIndex || comparator.WireOneIndex == wireTwoIndex
                        || comparator.WireTwoIndex == wireOneIndex || comparator.WireTwoIndex == wireTwoIndex;
        }

        private void UpdateLayerDepth(int layerIndex, int drawingLayerIndex)
        {
            int currentDepth;
            int newDepth = drawingLayerIndex + 1;

            if (LayersDepths.TryGetValue(layerIndex, out currentDepth))
            {
                if (newDepth > currentDepth)
                {
                    LayersDepths[layerIndex] = newDepth;
                }
            }
            else
            {
                LayersDepths.Add(layerIndex, newDepth);
            }
        }
    }
}
